package local.xinshi.netmgr.pkg;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;


import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

@Controller
@RequestMapping("/mgr")
public class MgrController {
    @Autowired
    SqlService sqlService;

    @Autowired
    SshService sshService;

    @Autowired
    ObjectMapper mapper;

    @Autowired
    ThreadPoolTaskScheduler scheduler;

    /**
     * 更新房间信息后需要调用此方法刷新状态
     */
    private void refreshState() {
        sshService.roomMap = sqlService.getRoom();
        sshService.ifMap.clear();
        sshService.roomMap.forEach((k, v) -> {
            sshService.ifMap.put(v, k);
        });
        sshService.getStates();
    }

    /**
     * 添加或修改房间信息
     * 
     * @param room 请求中的JSON对象映射的Room对象，如果oldname为空字符串则添加room
     */
    @PostMapping("/modroom")
    @ResponseBody
    public void modRoom(@RequestBody Room room) {
        StringBuilder sb = new StringBuilder();
        String[] args = null;
        if (room.getOldname().equals("")) {
            sb.append("insert into cfg_room values(?,?,?);");
            args = new String[] { room.getName(), room.getIface(), room.getItype() };
        } else {
            sb.append("update cfg_room set ");
            List<String> list = new ArrayList<>();
            if (room.getName() != null && !"".equals(room.getName())) {
                sb.append("name=?,");
                list.add(room.getName());
            }
            if (room.getIface() != null && !"".equals(room.getIface())) {
                sb.append("iface=?,");
                list.add(room.getIface());
            }
            if (room.getItype() != null && !"".equals(room.getItype())) {
                sb.append("itype=?,");
                list.add(room.getItype());
            }
            sb.deleteCharAt(sb.length() - 1).append(" where name=?;");
            list.add(room.getOldname());
            args = list.toArray(new String[list.size()]);
        }
        if (sqlService.updateTb(sb.toString(), args)) {
            this.refreshState();
        }
    }

    /**
     * 从指定表中删除指定数据
     * 
     * @param name 要删除的数据名称
     * @param tb   要操作的表的名称
     */
    @PostMapping("/delete")
    @ResponseBody
    public void del(@RequestParam String name, @RequestParam String tb) {
        if (sqlService.updateTb("delete from " + tb + " where name=?", name)) {
            if (tb.equals("cfg_room")) {
                this.refreshState();
                sshService.states.remove(name);
            }else if (tb.equals("cfg_users")){
                UserSessions.invalidate(name);
            }
        }
    }

    /**
     * 添加或修改用户信息
     * 
     * @param user 请求中的JSON对象映射的User对象，如果oldname为空字符串则添加user
     */
    @PostMapping("/moduser")
    @ResponseBody
    public void modUser(@RequestBody User user) {
        StringBuilder sb = new StringBuilder();
        String[] args = null;
        if (user.getOldname().equals("")) {
            sb.append("insert into cfg_users values(?,?,?);");
            args = new String[] { user.getName(), user.getPwd(), user.getRole() };
        } else {
            sb.append("update cfg_users set ");
            List<String> list = new ArrayList<>();
            if (user.getName() != null && !"".equals(user.getName())) {
                sb.append("name=?,");
                list.add(user.getName());
            }
            if (user.getPwd() != null && !"".equals(user.getPwd())) {
                sb.append("pwd=?,");
                list.add(user.getPwd());
            }
            if (user.getRole() != null && !"".equals(user.getRole())) {
                sb.append("role=?,");
                list.add(user.getRole());
            }
            sb.deleteCharAt(sb.length() - 1).append(" where name=?;");
            list.add(user.getOldname());
            args = list.toArray(new String[list.size()]);
            UserSessions.invalidate(user.getOldname());
        }
        sqlService.updateTb(sb.toString(), args);
    }

    /**
     * 修改参数表的数据(只允许修改变量值)
     * 
     * @param name 变量名
     * @param val  变量值
     */
    @PostMapping("/modarg")
    @ResponseBody
    public void modArg(@RequestParam String name, @RequestParam String val) {
        if (sqlService.updateTb("update cfg_args set val=? where name=?", val, name)) {
            switch (name) {
                case "SshHost":
                case "SshUser":
                case "SshPwd":
                    sshService.reconnect();
                    break;
                case "PermitAcl":
                    sshService.permitAcl = val;
                    sshService.getStates();
                    break;
                case "DenyAcl":
                    sshService.denyAcl = val;
                    sshService.getStates();
                    break;
                case "EnableAuto":
                    sshService.enableAuto = !val.equals("0");
                case "AutoDeny":
                    sshService.disableAutoDeny();
                    if (sshService.enableAuto) {
                        sshService.enableAutoDeny();
                    }
                    break;
                case "PortGroup":
                    sshService.portGroup = val;
                    break;
            }
        }
    }

    /**
     * 初始化数据库
     */
    @PostMapping("/initdb")
    @ResponseBody
    public void initDb() {
        sqlService.initDb();
        UserSessions.invalidate("");
        sshService.reconnect();
    }

    /**
     * 查询用户表的所有数据
     * 
     * @return [{'name':'?','pwd':'?','role':'?'},...]
     * @throws JsonProcessingException
     */
    @PostMapping(value = "/getusers", produces = "application/json;charset=utf-8")
    @ResponseBody
    public String getUsers() throws JsonProcessingException {
        return mapper.writeValueAsString(sqlService.listTable("cfg_users", "role", "name", "pwd", "role"));
    }

    /**
     * 查询房间表的所有数据
     * 
     * @return [{'name':'?','iface':'?','itype':'?'},...]
     * @throws JsonProcessingException
     */
    @PostMapping(value = "/getrooms", produces = "application/json;charset=utf-8")
    @ResponseBody
    public String getRooms() throws JsonProcessingException {
        return mapper.writeValueAsString(sqlService.listTable("cfg_room", "name", "name", "iface", "itype"));
    }

    /**
     * 查询参数表的所有数据
     * 
     * @return [{'name':'?','val':'?','desc':'?'},...]
     * @throws JsonProcessingException
     */
    @PostMapping(value = "/getargs", produces = "application/json;charset=utf-8")
    @ResponseBody
    public String getArgs() throws JsonProcessingException {
        return mapper.writeValueAsString(sqlService.listTable("cfg_args", "name", "name", "val", "desc"));
    }

    /**
     * 获取用户操作记录
     * 
     * @param pgNo 分页当前页码
     * @return [{"time":"?","action":"?"},....]
     * @throws JsonProcessingException
     */
    @PostMapping("/logs")
    @ResponseBody
    public String logs(@RequestParam int pgNo) throws JsonProcessingException {
        return mapper.writeValueAsString(sqlService.getLogs(pgNo));
    }

    /**
     * 清除一周前的操作记录
     */
    @PostMapping("/cleanlog")
    @ResponseBody
    public void cleanlog() {
        sqlService.cleanLog();
    }

    /**
     * 备份数据库，返回备份的文件流
     * 
     * @param bakname 备份的文件名
     * @throws InterruptedException
     */
    @RequestMapping("/backupdb/{bakname}")
    public ResponseEntity<Resource> backupDb(@PathVariable String bakname) throws IOException, InterruptedException {
        Runtime rt = Runtime.getRuntime();
        rt.exec("sqlite3 cfg.db \".backup " + bakname + "\"");
        Thread.sleep(3000);
        Resource resource = new UrlResource(Paths.get(bakname).normalize().toUri());
        if (resource.exists()) {
            String contentType = "application/octec-stream";
            scheduler.schedule(()->{
                try {
                    Files.delete(Paths.get(bakname));
                } catch (IOException e) {
                }
            }, Instant.now().plusSeconds(5));
            return ResponseEntity.ok().contentType(MediaType.parseMediaType(contentType))
                    .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
                    .body(resource);
        }
        return null;
    }

    /**
     * 检测将要上传的文件是否是有效的sqlite数据库的备份文件
     * @param head
     * @return
     */
    @PostMapping("/validatebak")
    @ResponseBody
    public String valibak(@RequestParam String head){
        return head.equals("SQLite format 3")?"ok":"";
    }

    /**
     * 还原数据库
     */
    @PostMapping(value = "/restore",consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
    @ResponseBody
    public String restore(@RequestParam MultipartFile file) throws IOException {
        if(file.isEmpty()) return "文件不能为空";
        byte[] b = Arrays.copyOf(file.getBytes(), 15);
        if(!new String(b).equals("SQLite format 3")) return "不是有效的备份文件";
        String fname = file.getOriginalFilename();
        file.transferTo(Paths.get(fname));
        Runtime rt = Runtime.getRuntime();
        rt.exec("sqlite3 cfg.db \".restore " + fname + "\"");
        scheduler.schedule(()->{
            try {
                Files.delete(Paths.get(fname));
            } catch (IOException e) {
            }
        }, Instant.now().plusSeconds(3));
        sshService.reconnect();
        return "ok";
    }
}